using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Web;
using System.Web.Caching;
using Janrain.OpenId;


namespace Janrain.OpenId.Store
{

	public class MemoryStore : IAssociationStore
	{

		#region Member Variables

		private static MemoryStore local_instance = new MemoryStore();
		private Cache cache = HttpRuntime.Cache;

		#endregion

		#region Constructor(s)

		private MemoryStore()
		{

	    
		}

		#endregion

		#region Private Properties

		private Hashtable ServerAssocsTable
		{
			get
			{
				Hashtable server_assocs = (Hashtable)this.cache["OpenId.serverAssocs"];

				if (server_assocs == null)
					server_assocs = new Hashtable();

				return server_assocs;
			}
			set
			{
				this.cache["OpenId.serverAssocs"] = value;
			}
		}

		private Hashtable Nonces
		{
			get
			{
				Hashtable nonces = (Hashtable)this.cache["OpenId.nonces"];

				if (nonces == null)
					nonces = new Hashtable();

				return nonces;
			}
			set
			{
				this.cache["OpenId.nonces"] = value;
			}
		}

		#endregion

		#region Methods

		public static MemoryStore GetInstance()
		{
			return local_instance;
		}

		public ServerAssocs GetServerAssocs(Uri server_url)
		{
			lock (this)
			{
				Hashtable table = this.ServerAssocsTable;

				if (!table.ContainsKey(server_url))
				{
					table.Add(server_url, new ServerAssocs());
					this.ServerAssocsTable = table;
				}

				return (ServerAssocs)table[server_url];
			}
		}

		public void StoreAssociation(Uri server_url, Association assoc)
		{
			lock (this)
			{
				Hashtable table = this.ServerAssocsTable;

				if (!table.ContainsKey(server_url))
					table.Add(server_url, new ServerAssocs());

				ServerAssocs server_assocs = (ServerAssocs)table[server_url];

				server_assocs.Set(assoc);

				this.ServerAssocsTable = table;
			}
		}

		public Association GetAssociation(Uri serverUri)
		{
			lock (this)
			{
				return GetServerAssocs(serverUri).Best();
			}
		}

		public Association GetAssociation(Uri serverUri, string handle)
		{
			lock (this)
			{
				return GetServerAssocs(serverUri).Get(handle);
			}
		}

		public bool RemoveAssociation(Uri serverUri, string handle)
		{
			lock (this)
			{
				return GetServerAssocs(serverUri).Remove(handle);
			}
		}

		public bool StoreNonce(string nonce)
		{
			lock (this)
			{
				Hashtable nonces = this.Nonces;
				bool present = nonces.ContainsKey(nonce);

				nonces.Remove(nonce);
				this.Nonces = nonces;

				return present;

			}
		}

		public bool UseNonce(string nonce)
		{
			lock (this)
			{
				Hashtable nonces = this.Nonces;
				bool present = nonces.ContainsKey(nonce);

				nonces.Remove(nonce);
				this.Nonces = nonces;

				return present;

			}
		}

		#endregion

		#region IAssociationStore Members

		byte[] IAssociationStore.AuthKey
		{
			get
			{
				lock (this)
				{
					byte[] auth_key = (byte[])this.cache["OpenId.authKey"];

					if (auth_key == null)
					{
						auth_key = new byte[20];
						new RNGCryptoServiceProvider().GetBytes(auth_key);
						this.cache.Insert("OpenId.authKey", auth_key);
					}
					return auth_key;
				}
			}
		}

		bool IAssociationStore.IsDumb
		{
			get { return false; }
		}

		void IAssociationStore.StoreAssociation(Uri serverUri, Association assoc)
		{
			this.StoreAssociation(serverUri, assoc);
		}

		Association IAssociationStore.GetAssociation(Uri serverUri)
		{
			return this.GetAssociation(serverUri);
		}

		Association IAssociationStore.GetAssociation(Uri serverUri, string handle)
		{
			return this.GetAssociation(serverUri, handle);
		}

		bool IAssociationStore.RemoveAssociation(Uri serverUri, string handle)
		{
			return this.RemoveAssociation(serverUri, handle);
		}

		void IAssociationStore.StoreNonce(string nonce)
		{
			this.StoreNonce(nonce);
		}

		bool IAssociationStore.UseNonce(string nonce)
		{
			return this.UseNonce(nonce);
		}

		#endregion

	}

	// TODO Move this class out to it's own file
	public class ServerAssocs
	{

		#region Private Members

		private Hashtable assocs;

		#endregion

		#region Constructor(s)

		public ServerAssocs()
		{
			this.assocs = new Hashtable();
		}

		#endregion

		#region Methods

		public void Set(Association assoc)
		{
			this.assocs[assoc.Handle] = assoc;
		}

		public Association Get(string handle)
		{
			Association assoc = null;

			if (this.assocs.Contains(handle))
				assoc = (Association)this.assocs[handle];

			return assoc;
		}

		public bool Remove(string handle)
		{
			bool present = this.assocs.Contains(handle);

			this.assocs.Remove(handle);

			return present;
		}

		public Association Best()
		{
			Association best = null;

			foreach (Association assoc in this.assocs.Values)
			{
				if (best == null || best.Issued < assoc.Issued)
					best = assoc;
			}

			return best;
		}

		#endregion

	}

}
